/*
 * Copyright (c) 2002, 2008, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package javax.management;

import static com.sun.jmx.defaults.JmxProperties.MISC_LOGGER;

import com.sun.jmx.mbeanserver.DescriptorCache;
import com.sun.jmx.mbeanserver.Introspector;
import com.sun.jmx.mbeanserver.MBeanSupport;
import com.sun.jmx.mbeanserver.MXBeanSupport;
import com.sun.jmx.mbeanserver.StandardMBeanSupport;
import com.sun.jmx.mbeanserver.Util;

import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import java.util.logging.Level;
import javax.management.openmbean.OpenMBeanAttributeInfo;
import javax.management.openmbean.OpenMBeanAttributeInfoSupport;
import javax.management.openmbean.OpenMBeanConstructorInfo;
import javax.management.openmbean.OpenMBeanConstructorInfoSupport;
import javax.management.openmbean.OpenMBeanOperationInfo;
import javax.management.openmbean.OpenMBeanOperationInfoSupport;
import javax.management.openmbean.OpenMBeanParameterInfo;
import javax.management.openmbean.OpenMBeanParameterInfoSupport;

/**
 * <p>An MBean whose management interface is determined by reflection
 * on a Java interface.</p>
 *
 * <p>This class brings more flexibility to the notion of Management
 * Interface in the use of Standard MBeans.  Straightforward use of
 * the patterns for Standard MBeans described in the JMX Specification
 * means that there is a fixed relationship between the implementation
 * class of an MBean and its management interface (i.e., if the
 * implementation class is Thing, the management interface must be
 * ThingMBean).  This class makes it possible to keep the convenience
 * of specifying the management interface with a Java interface,
 * without requiring that there be any naming relationship between the
 * implementation and interface classes.</p>
 *
 * <p>By making a DynamicMBean out of an MBean, this class makes
 * it possible to select any interface implemented by the MBean as its
 * management interface, provided that it complies with JMX patterns
 * (i.e., attributes defined by getter/setter etc...).</p>
 *
 * <p> This class also provides hooks that make it possible to supply
 * custom descriptions and names for the {@link MBeanInfo} returned by
 * the DynamicMBean interface.</p>
 *
 * <p>Using this class, an MBean can be created with any
 * implementation class name <i>Impl</i> and with a management
 * interface defined (as for current Standard MBeans) by any interface
 * <i>Intf</i>, in one of two general ways:</p>
 *
 * <ul>
 *
 * <li>Using the public constructor
 * {@link #StandardMBean(java.lang.Object, java.lang.Class, boolean)
 * StandardMBean(impl,interface)}:
 * <pre>
 *     MBeanServer mbs;
 *     ...
 *     Impl impl = new Impl(...);
 *     StandardMBean mbean = new StandardMBean(impl, Intf.class, false);
 *     mbs.registerMBean(mbean, objectName);
 *     </pre></li>
 *
 * <li>Subclassing StandardMBean:
 * <pre>
 *     public class Impl extends StandardMBean implements Intf {
 *        public Impl() {
 *          super(Intf.class, false);
 *       }
 *       // implement methods of Intf
 *     }
 *
 *     [...]
 *
 *     MBeanServer mbs;
 *     ....
 *     Impl impl = new Impl();
 *     mbs.registerMBean(impl, objectName);
 *     </pre></li>
 *
 * </ul>
 *
 * <p>In either case, the class <i>Impl</i> must implement the
 * interface <i>Intf</i>.</p>
 *
 * <p>Standard MBeans based on the naming relationship between
 * implementation and interface classes are of course still
 * available.</p>
 *
 * <p>This class may also be used to construct MXBeans.  The usage
 * is exactly the same as for Standard MBeans except that in the
 * examples above, the {@code false} parameter to the constructor or
 * {@code super(...)} invocation is instead {@code true}.</p>
 *
 * @since 1.5
 */
public class StandardMBean implements DynamicMBean, MBeanRegistration {

  private final static DescriptorCache descriptors =
      DescriptorCache.getInstance(JMX.proof);

  /**
   * The DynamicMBean that wraps the MXBean or Standard MBean implementation.
   **/
  private volatile MBeanSupport<?> mbean;

  /**
   * The cached MBeanInfo.
   **/
  private volatile MBeanInfo cachedMBeanInfo;

  /**
   * Make a DynamicMBean out of <var>implementation</var>, using the
   * specified <var>mbeanInterface</var> class.
   *
   * @param implementation The implementation of this MBean. If <code>null</code>, and null
   * implementation is allowed, then the implementation is assumed to be <var>this</var>.
   * @param mbeanInterface The Management Interface exported by this MBean's implementation. If
   * <code>null</code>, then this object will use standard JMX design pattern to determine the
   * management interface associated with the given implementation.
   * @param nullImplementationAllowed <code>true</code> if a null implementation is allowed. If null
   * implementation is allowed, and a null implementation is passed, then the implementation is
   * assumed to be <var>this</var>.
   * @throws IllegalArgumentException if the given <var>implementation</var> is null, and null is
   * not allowed.
   **/
  private <T> void construct(T implementation, Class<T> mbeanInterface,
      boolean nullImplementationAllowed,
      boolean isMXBean)
      throws NotCompliantMBeanException {
    if (implementation == null) {
      // Have to use (T)this rather than mbeanInterface.cast(this)
      // because mbeanInterface might be null.
      if (nullImplementationAllowed) {
        implementation = Util.<T>cast(this);
      } else {
        throw new IllegalArgumentException("implementation is null");
      }
    }
    if (isMXBean) {
      if (mbeanInterface == null) {
        mbeanInterface = Util.cast(Introspector.getMXBeanInterface(
            implementation.getClass()));
      }
      this.mbean = new MXBeanSupport(implementation, mbeanInterface);
    } else {
      if (mbeanInterface == null) {
        mbeanInterface = Util.cast(Introspector.getStandardMBeanInterface(
            implementation.getClass()));
      }
      this.mbean =
          new StandardMBeanSupport(implementation, mbeanInterface);
    }
  }

  /**
   * <p>Make a DynamicMBean out of the object
   * <var>implementation</var>, using the specified
   * <var>mbeanInterface</var> class.</p>
   *
   * @param implementation The implementation of this MBean.
   * @param mbeanInterface The Management Interface exported by this MBean's implementation. If
   * <code>null</code>, then this object will use standard JMX design pattern to determine the
   * management interface associated with the given implementation.
   * @param <T> Allows the compiler to check that {@code implementation} does indeed implement the
   * class described by {@code mbeanInterface}.  The compiler can only check this if {@code
   * mbeanInterface} is a class literal such as {@code MyMBean.class}.
   * @throws IllegalArgumentException if the given <var>implementation</var> is null.
   * @throws NotCompliantMBeanException if the <var>mbeanInterface</var> does not follow JMX design
   * patterns for Management Interfaces, or if the given <var>implementation</var> does not
   * implement the specified interface.
   **/
  public <T> StandardMBean(T implementation, Class<T> mbeanInterface)
      throws NotCompliantMBeanException {
    construct(implementation, mbeanInterface, false, false);
  }

  /**
   * <p>Make a DynamicMBean out of <var>this</var>, using the specified
   * <var>mbeanInterface</var> class.</p>
   *
   * <p>Calls {@link #StandardMBean(java.lang.Object, java.lang.Class)
   * this(this,mbeanInterface)}.
   * This constructor is reserved to subclasses.</p>
   *
   * @param mbeanInterface The Management Interface exported by this MBean.
   * @throws NotCompliantMBeanException if the <var>mbeanInterface</var> does not follow JMX design
   * patterns for Management Interfaces, or if <var>this</var> does not implement the specified
   * interface.
   **/
  protected StandardMBean(Class<?> mbeanInterface)
      throws NotCompliantMBeanException {
    construct(null, mbeanInterface, true, false);
  }

  /**
   * <p>Make a DynamicMBean out of the object
   * <var>implementation</var>, using the specified
   * <var>mbeanInterface</var> class, and choosing whether the
   * resultant MBean is an MXBean.  This constructor can be used
   * to make either Standard MBeans or MXBeans.  Unlike the
   * constructor {@link #StandardMBean(Object, Class)}, it
   * does not throw NotCompliantMBeanException.</p>
   *
   * @param implementation The implementation of this MBean.
   * @param mbeanInterface The Management Interface exported by this MBean's implementation. If
   * <code>null</code>, then this object will use standard JMX design pattern to determine the
   * management interface associated with the given implementation.
   * @param isMXBean If true, the {@code mbeanInterface} parameter names an MXBean interface and the
   * resultant MBean is an MXBean.
   * @param <T> Allows the compiler to check that {@code implementation} does indeed implement the
   * class described by {@code mbeanInterface}.  The compiler can only check this if {@code
   * mbeanInterface} is a class literal such as {@code MyMBean.class}.
   * @throws IllegalArgumentException if the given <var>implementation</var> is null, or if the
   * <var>mbeanInterface</var> does not follow JMX design patterns for Management Interfaces, or if
   * the given <var>implementation</var> does not implement the specified interface.
   * @since 1.6
   **/
  public <T> StandardMBean(T implementation, Class<T> mbeanInterface,
      boolean isMXBean) {
    try {
      construct(implementation, mbeanInterface, false, isMXBean);
    } catch (NotCompliantMBeanException e) {
      throw new IllegalArgumentException(e);
    }
  }

  /**
   * <p>Make a DynamicMBean out of <var>this</var>, using the specified
   * <var>mbeanInterface</var> class, and choosing whether the resulting
   * MBean is an MXBean.  This constructor can be used
   * to make either Standard MBeans or MXBeans.  Unlike the
   * constructor {@link #StandardMBean(Object, Class)}, it
   * does not throw NotCompliantMBeanException.</p>
   *
   * <p>Calls {@link #StandardMBean(java.lang.Object, java.lang.Class, boolean)
   * this(this, mbeanInterface, isMXBean)}.
   * This constructor is reserved to subclasses.</p>
   *
   * @param mbeanInterface The Management Interface exported by this MBean.
   * @param isMXBean If true, the {@code mbeanInterface} parameter names an MXBean interface and the
   * resultant MBean is an MXBean.
   * @throws IllegalArgumentException if the <var>mbeanInterface</var> does not follow JMX design
   * patterns for Management Interfaces, or if <var>this</var> does not implement the specified
   * interface.
   * @since 1.6
   **/
  protected StandardMBean(Class<?> mbeanInterface, boolean isMXBean) {
    try {
      construct(null, mbeanInterface, true, isMXBean);
    } catch (NotCompliantMBeanException e) {
      throw new IllegalArgumentException(e);
    }
  }

  /**
   * <p>Replace the implementation object wrapped in this object.</p>
   *
   * @param implementation The new implementation of this Standard MBean (or MXBean). The
   * <code>implementation</code> object must implement the Standard MBean (or MXBean) interface that
   * was supplied when this <code>StandardMBean</code> was constructed.
   * @throws IllegalArgumentException if the given <var>implementation</var> is null.
   * @throws NotCompliantMBeanException if the given <var>implementation</var> does not implement
   * the Standard MBean (or MXBean) interface that was supplied at construction.
   * @see #getImplementation
   **/
  public void setImplementation(Object implementation)
      throws NotCompliantMBeanException {

    if (implementation == null) {
      throw new IllegalArgumentException("implementation is null");
    }

    if (isMXBean()) {
      this.mbean = new MXBeanSupport(implementation,
          Util.<Class<Object>>cast(getMBeanInterface()));
    } else {
      this.mbean = new StandardMBeanSupport(implementation,
          Util.<Class<Object>>cast(getMBeanInterface()));
    }
  }

  /**
   * Get the implementation of this Standard MBean (or MXBean).
   *
   * @return The implementation of this Standard MBean (or MXBean).
   * @see #setImplementation
   **/
  public Object getImplementation() {
    return mbean.getResource();
  }

  /**
   * Get the Management Interface of this Standard MBean (or MXBean).
   *
   * @return The management interface of this Standard MBean (or MXBean).
   **/
  public final Class<?> getMBeanInterface() {
    return mbean.getMBeanInterface();
  }

  /**
   * Get the class of the implementation of this Standard MBean (or MXBean).
   *
   * @return The class of the implementation of this Standard MBean (or MXBean).
   **/
  public Class<?> getImplementationClass() {
    return mbean.getResource().getClass();
  }

  // ------------------------------------------------------------------
  // From the DynamicMBean interface.
  // ------------------------------------------------------------------
  public Object getAttribute(String attribute)
      throws AttributeNotFoundException,
      MBeanException,
      ReflectionException {
    return mbean.getAttribute(attribute);
  }

  // ------------------------------------------------------------------
  // From the DynamicMBean interface.
  // ------------------------------------------------------------------
  public void setAttribute(Attribute attribute)
      throws AttributeNotFoundException,
      InvalidAttributeValueException,
      MBeanException,
      ReflectionException {
    mbean.setAttribute(attribute);
  }

  // ------------------------------------------------------------------
  // From the DynamicMBean interface.
  // ------------------------------------------------------------------
  public AttributeList getAttributes(String[] attributes) {
    return mbean.getAttributes(attributes);
  }

  // ------------------------------------------------------------------
  // From the DynamicMBean interface.
  // ------------------------------------------------------------------
  public AttributeList setAttributes(AttributeList attributes) {
    return mbean.setAttributes(attributes);
  }

  // ------------------------------------------------------------------
  // From the DynamicMBean interface.
  // ------------------------------------------------------------------
  public Object invoke(String actionName, Object params[], String signature[])
      throws MBeanException, ReflectionException {
    return mbean.invoke(actionName, params, signature);
  }

  /**
   * Get the {@link MBeanInfo} for this MBean.
   * <p>
   * This method implements
   * {@link javax.management.DynamicMBean#getMBeanInfo()
   * DynamicMBean.getMBeanInfo()}.
   * <p>
   * This method first calls {@link #getCachedMBeanInfo()} in order to
   * retrieve the cached MBeanInfo for this MBean, if any. If the
   * MBeanInfo returned by {@link #getCachedMBeanInfo()} is not null,
   * then it is returned.<br>
   * Otherwise, this method builds a default MBeanInfo for this MBean,
   * using the Management Interface specified for this MBean.
   * <p>
   * While building the MBeanInfo, this method calls the customization
   * hooks that make it possible for subclasses to supply their custom
   * descriptions, parameter names, etc...<br>
   * Finally, it calls {@link #cacheMBeanInfo(javax.management.MBeanInfo)
   * cacheMBeanInfo()} in order to cache the new MBeanInfo.
   *
   * @return The cached MBeanInfo for that MBean, if not null, or a newly built MBeanInfo if none
   * was cached.
   **/
  public MBeanInfo getMBeanInfo() {
    try {
      final MBeanInfo cached = getCachedMBeanInfo();
      if (cached != null) {
        return cached;
      }
    } catch (RuntimeException x) {
      if (MISC_LOGGER.isLoggable(Level.FINEST)) {
        MISC_LOGGER.logp(Level.FINEST,
            MBeanServerFactory.class.getName(), "getMBeanInfo",
            "Failed to get cached MBeanInfo", x);
      }
    }

    if (MISC_LOGGER.isLoggable(Level.FINER)) {
      MISC_LOGGER.logp(Level.FINER,
          MBeanServerFactory.class.getName(), "getMBeanInfo",
          "Building MBeanInfo for " +
              getImplementationClass().getName());
    }

    MBeanSupport<?> msupport = mbean;
    final MBeanInfo bi = msupport.getMBeanInfo();
    final Object impl = msupport.getResource();

    final boolean immutableInfo = immutableInfo(this.getClass());

    final String cname = getClassName(bi);
    final String text = getDescription(bi);
    final MBeanConstructorInfo[] ctors = getConstructors(bi, impl);
    final MBeanAttributeInfo[] attrs = getAttributes(bi);
    final MBeanOperationInfo[] ops = getOperations(bi);
    final MBeanNotificationInfo[] ntfs = getNotifications(bi);
    final Descriptor desc = getDescriptor(bi, immutableInfo);

    final MBeanInfo nmbi = new MBeanInfo(
        cname, text, attrs, ctors, ops, ntfs, desc);
    try {
      cacheMBeanInfo(nmbi);
    } catch (RuntimeException x) {
      if (MISC_LOGGER.isLoggable(Level.FINEST)) {
        MISC_LOGGER.logp(Level.FINEST,
            MBeanServerFactory.class.getName(), "getMBeanInfo",
            "Failed to cache MBeanInfo", x);
      }
    }

    return nmbi;
  }

  /**
   * Customization hook:
   * Get the className that will be used in the MBeanInfo returned by
   * this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom class name.  The default implementation returns
   * {@link MBeanInfo#getClassName() info.getClassName()}.
   *
   * @param info The default MBeanInfo derived by reflection.
   * @return the class name for the new MBeanInfo.
   **/
  protected String getClassName(MBeanInfo info) {
    if (info == null) {
      return getImplementationClass().getName();
    }
    return info.getClassName();
  }

  /**
   * Customization hook:
   * Get the description that will be used in the MBeanInfo returned by
   * this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom MBean description.  The default implementation returns
   * {@link MBeanInfo#getDescription() info.getDescription()}.
   *
   * @param info The default MBeanInfo derived by reflection.
   * @return the description for the new MBeanInfo.
   **/
  protected String getDescription(MBeanInfo info) {
    if (info == null) {
      return null;
    }
    return info.getDescription();
  }

  /**
   * <p>Customization hook:
   * Get the description that will be used in the MBeanFeatureInfo
   * returned by this MBean.</p>
   *
   * <p>Subclasses may redefine this method in order to supply
   * their custom description.  The default implementation returns
   * {@link MBeanFeatureInfo#getDescription()
   * info.getDescription()}.</p>
   *
   * <p>This method is called by
   * {@link #getDescription(MBeanAttributeInfo)},
   * {@link #getDescription(MBeanOperationInfo)},
   * {@link #getDescription(MBeanConstructorInfo)}.</p>
   *
   * @param info The default MBeanFeatureInfo derived by reflection.
   * @return the description for the given MBeanFeatureInfo.
   **/
  protected String getDescription(MBeanFeatureInfo info) {
    if (info == null) {
      return null;
    }
    return info.getDescription();
  }

  /**
   * Customization hook:
   * Get the description that will be used in the MBeanAttributeInfo
   * returned by this MBean.
   *
   * <p>Subclasses may redefine this method in order to supply their
   * custom description.  The default implementation returns {@link
   * #getDescription(MBeanFeatureInfo)
   * getDescription((MBeanFeatureInfo) info)}.
   *
   * @param info The default MBeanAttributeInfo derived by reflection.
   * @return the description for the given MBeanAttributeInfo.
   **/
  protected String getDescription(MBeanAttributeInfo info) {
    return getDescription((MBeanFeatureInfo) info);
  }

  /**
   * Customization hook:
   * Get the description that will be used in the MBeanConstructorInfo
   * returned by this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom description.
   * The default implementation returns {@link
   * #getDescription(MBeanFeatureInfo)
   * getDescription((MBeanFeatureInfo) info)}.
   *
   * @param info The default MBeanConstructorInfo derived by reflection.
   * @return the description for the given MBeanConstructorInfo.
   **/
  protected String getDescription(MBeanConstructorInfo info) {
    return getDescription((MBeanFeatureInfo) info);
  }

  /**
   * Customization hook:
   * Get the description that will be used for the  <var>sequence</var>
   * MBeanParameterInfo of the MBeanConstructorInfo returned by this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom description.  The default implementation returns
   * {@link MBeanParameterInfo#getDescription() param.getDescription()}.
   *
   * @param ctor The default MBeanConstructorInfo derived by reflection.
   * @param param The default MBeanParameterInfo derived by reflection.
   * @param sequence The sequence number of the parameter considered ("0" for the first parameter,
   * "1" for the second parameter, etc...).
   * @return the description for the given MBeanParameterInfo.
   **/
  protected String getDescription(MBeanConstructorInfo ctor,
      MBeanParameterInfo param,
      int sequence) {
    if (param == null) {
      return null;
    }
    return param.getDescription();
  }

  /**
   * Customization hook:
   * Get the name that will be used for the <var>sequence</var>
   * MBeanParameterInfo of the MBeanConstructorInfo returned by this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom parameter name.  The default implementation returns
   * {@link MBeanParameterInfo#getName() param.getName()}.
   *
   * @param ctor The default MBeanConstructorInfo derived by reflection.
   * @param param The default MBeanParameterInfo derived by reflection.
   * @param sequence The sequence number of the parameter considered ("0" for the first parameter,
   * "1" for the second parameter, etc...).
   * @return the name for the given MBeanParameterInfo.
   **/
  protected String getParameterName(MBeanConstructorInfo ctor,
      MBeanParameterInfo param,
      int sequence) {
    if (param == null) {
      return null;
    }
    return param.getName();
  }

  /**
   * Customization hook:
   * Get the description that will be used in the MBeanOperationInfo
   * returned by this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom description.  The default implementation returns
   * {@link #getDescription(MBeanFeatureInfo)
   * getDescription((MBeanFeatureInfo) info)}.
   *
   * @param info The default MBeanOperationInfo derived by reflection.
   * @return the description for the given MBeanOperationInfo.
   **/
  protected String getDescription(MBeanOperationInfo info) {
    return getDescription((MBeanFeatureInfo) info);
  }

  /**
   * Customization hook:
   * Get the <var>impact</var> flag of the operation that will be used in
   * the MBeanOperationInfo returned by this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom impact flag.  The default implementation returns
   * {@link MBeanOperationInfo#getImpact() info.getImpact()}.
   *
   * @param info The default MBeanOperationInfo derived by reflection.
   * @return the impact flag for the given MBeanOperationInfo.
   **/
  protected int getImpact(MBeanOperationInfo info) {
    if (info == null) {
      return MBeanOperationInfo.UNKNOWN;
    }
    return info.getImpact();
  }

  /**
   * Customization hook:
   * Get the name that will be used for the <var>sequence</var>
   * MBeanParameterInfo of the MBeanOperationInfo returned by this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom parameter name.  The default implementation returns
   * {@link MBeanParameterInfo#getName() param.getName()}.
   *
   * @param op The default MBeanOperationInfo derived by reflection.
   * @param param The default MBeanParameterInfo derived by reflection.
   * @param sequence The sequence number of the parameter considered ("0" for the first parameter,
   * "1" for the second parameter, etc...).
   * @return the name to use for the given MBeanParameterInfo.
   **/
  protected String getParameterName(MBeanOperationInfo op,
      MBeanParameterInfo param,
      int sequence) {
    if (param == null) {
      return null;
    }
    return param.getName();
  }

  /**
   * Customization hook:
   * Get the description that will be used for the  <var>sequence</var>
   * MBeanParameterInfo of the MBeanOperationInfo returned by this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom description.  The default implementation returns
   * {@link MBeanParameterInfo#getDescription() param.getDescription()}.
   *
   * @param op The default MBeanOperationInfo derived by reflection.
   * @param param The default MBeanParameterInfo derived by reflection.
   * @param sequence The sequence number of the parameter considered ("0" for the first parameter,
   * "1" for the second parameter, etc...).
   * @return the description for the given MBeanParameterInfo.
   **/
  protected String getDescription(MBeanOperationInfo op,
      MBeanParameterInfo param,
      int sequence) {
    if (param == null) {
      return null;
    }
    return param.getDescription();
  }

  /**
   * Customization hook:
   * Get the MBeanConstructorInfo[] that will be used in the MBeanInfo
   * returned by this MBean.
   * <br>
   * By default, this method returns <code>null</code> if the wrapped
   * implementation is not <var>this</var>. Indeed, if the wrapped
   * implementation is not this object itself, it will not be possible
   * to recreate a wrapped implementation by calling the implementation
   * constructors through <code>MBeanServer.createMBean(...)</code>.<br>
   * Otherwise, if the wrapped implementation is <var>this</var>,
   * <var>ctors</var> is returned.
   * <br>
   * Subclasses may redefine this method in order to modify this
   * behavior, if needed.
   *
   * @param ctors The default MBeanConstructorInfo[] derived by reflection.
   * @param impl The wrapped implementation. If <code>null</code> is passed, the wrapped
   * implementation is ignored and <var>ctors</var> is returned.
   * @return the MBeanConstructorInfo[] for the new MBeanInfo.
   **/
  protected MBeanConstructorInfo[]
  getConstructors(MBeanConstructorInfo[] ctors, Object impl) {
    if (ctors == null) {
      return null;
    }
    if (impl != null && impl != this) {
      return null;
    }
    return ctors;
  }

  /**
   * Customization hook:
   * Get the MBeanNotificationInfo[] that will be used in the MBeanInfo
   * returned by this MBean.
   * <br>
   * Subclasses may redefine this method in order to supply their
   * custom notifications.
   *
   * @param info The default MBeanInfo derived by reflection.
   * @return the MBeanNotificationInfo[] for the new MBeanInfo.
   **/
  MBeanNotificationInfo[] getNotifications(MBeanInfo info) {
    return null;
  }

  /**
   * <p>Get the Descriptor that will be used in the MBeanInfo
   * returned by this MBean.</p>
   *
   * <p>Subclasses may redefine this method in order to supply
   * their custom descriptor.</p>
   *
   * <p>The default implementation of this method returns a Descriptor
   * that contains at least the field {@code interfaceClassName}, with
   * value {@link #getMBeanInterface()}.getName(). It may also contain
   * the field {@code immutableInfo}, with a value that is the string
   * {@code "true"} if the implementation can determine that the
   * {@code MBeanInfo} returned by {@link #getMBeanInfo()} will always
   * be the same. It may contain other fields: fields defined by the
   * JMX specification must have appropriate values, and other fields
   * must follow the conventions for non-standard field names.</p>
   *
   * @param info The default MBeanInfo derived by reflection.
   * @return the Descriptor for the new MBeanInfo.
   */
  Descriptor getDescriptor(MBeanInfo info, boolean immutableInfo) {
    ImmutableDescriptor desc;
    if (info == null ||
        info.getDescriptor() == null ||
        info.getDescriptor().getFieldNames().length == 0) {
      final String interfaceClassNameS =
          "interfaceClassName=" + getMBeanInterface().getName();
      final String immutableInfoS =
          "immutableInfo=" + immutableInfo;
      desc = new ImmutableDescriptor(interfaceClassNameS, immutableInfoS);
      desc = descriptors.get(desc);
    } else {
      Descriptor d = info.getDescriptor();
      Map<String, Object> fields = new HashMap<String, Object>();
      for (String fieldName : d.getFieldNames()) {
        if (fieldName.equals("immutableInfo")) {
          // Replace immutableInfo as the underlying MBean/MXBean
          // could already implement NotificationBroadcaster and
          // return immutableInfo=true in its MBeanInfo.
          fields.put(fieldName, Boolean.toString(immutableInfo));
        } else {
          fields.put(fieldName, d.getFieldValue(fieldName));
        }
      }
      desc = new ImmutableDescriptor(fields);
    }
    return desc;
  }

  /**
   * Customization hook:
   * Return the MBeanInfo cached for this object.
   *
   * <p>Subclasses may redefine this method in order to implement their
   * own caching policy.  The default implementation stores one
   * {@link MBeanInfo} object per instance.
   *
   * @return The cached MBeanInfo, or null if no MBeanInfo is cached.
   * @see #cacheMBeanInfo(MBeanInfo)
   **/
  protected MBeanInfo getCachedMBeanInfo() {
    return cachedMBeanInfo;
  }

  /**
   * Customization hook:
   * cache the MBeanInfo built for this object.
   *
   * <p>Subclasses may redefine this method in order to implement
   * their own caching policy.  The default implementation stores
   * <code>info</code> in this instance.  A subclass can define
   * other policies, such as not saving <code>info</code> (so it is
   * reconstructed every time {@link #getMBeanInfo()} is called) or
   * sharing a unique {@link MBeanInfo} object when several
   * <code>StandardMBean</code> instances have equal {@link
   * MBeanInfo} values.
   *
   * @param info the new <code>MBeanInfo</code> to cache.  Any previously cached value is discarded.
   * This parameter may be null, in which case there is no new cached value.
   **/
  protected void cacheMBeanInfo(MBeanInfo info) {
    cachedMBeanInfo = info;
  }

  private boolean isMXBean() {
    return mbean.isMXBean();
  }

  private static <T> boolean identicalArrays(T[] a, T[] b) {
    if (a == b) {
      return true;
    }
    if (a == null || b == null || a.length != b.length) {
      return false;
    }
    for (int i = 0; i < a.length; i++) {
      if (a[i] != b[i]) {
        return false;
      }
    }
    return true;
  }

  private static <T> boolean equal(T a, T b) {
    if (a == b) {
      return true;
    }
    if (a == null || b == null) {
      return false;
    }
    return a.equals(b);
  }

  private static MBeanParameterInfo
  customize(MBeanParameterInfo pi,
      String name,
      String description) {
    if (equal(name, pi.getName()) &&
        equal(description, pi.getDescription())) {
      return pi;
    } else if (pi instanceof OpenMBeanParameterInfo) {
      OpenMBeanParameterInfo opi = (OpenMBeanParameterInfo) pi;
      return new OpenMBeanParameterInfoSupport(name,
          description,
          opi.getOpenType(),
          pi.getDescriptor());
    } else {
      return new MBeanParameterInfo(name,
          pi.getType(),
          description,
          pi.getDescriptor());
    }
  }

  private static MBeanConstructorInfo
  customize(MBeanConstructorInfo ci,
      String description,
      MBeanParameterInfo[] signature) {
    if (equal(description, ci.getDescription()) &&
        identicalArrays(signature, ci.getSignature())) {
      return ci;
    }
    if (ci instanceof OpenMBeanConstructorInfo) {
      OpenMBeanParameterInfo[] oparams =
          paramsToOpenParams(signature);
      return new OpenMBeanConstructorInfoSupport(ci.getName(),
          description,
          oparams,
          ci.getDescriptor());
    } else {
      return new MBeanConstructorInfo(ci.getName(),
          description,
          signature,
          ci.getDescriptor());
    }
  }

  private static MBeanOperationInfo
  customize(MBeanOperationInfo oi,
      String description,
      MBeanParameterInfo[] signature,
      int impact) {
    if (equal(description, oi.getDescription()) &&
        identicalArrays(signature, oi.getSignature()) &&
        impact == oi.getImpact()) {
      return oi;
    }
    if (oi instanceof OpenMBeanOperationInfo) {
      OpenMBeanOperationInfo ooi = (OpenMBeanOperationInfo) oi;
      OpenMBeanParameterInfo[] oparams =
          paramsToOpenParams(signature);
      return new OpenMBeanOperationInfoSupport(oi.getName(),
          description,
          oparams,
          ooi.getReturnOpenType(),
          impact,
          oi.getDescriptor());
    } else {
      return new MBeanOperationInfo(oi.getName(),
          description,
          signature,
          oi.getReturnType(),
          impact,
          oi.getDescriptor());
    }
  }

  private static MBeanAttributeInfo
  customize(MBeanAttributeInfo ai,
      String description) {
    if (equal(description, ai.getDescription())) {
      return ai;
    }
    if (ai instanceof OpenMBeanAttributeInfo) {
      OpenMBeanAttributeInfo oai = (OpenMBeanAttributeInfo) ai;
      return new OpenMBeanAttributeInfoSupport(ai.getName(),
          description,
          oai.getOpenType(),
          ai.isReadable(),
          ai.isWritable(),
          ai.isIs(),
          ai.getDescriptor());
    } else {
      return new MBeanAttributeInfo(ai.getName(),
          ai.getType(),
          description,
          ai.isReadable(),
          ai.isWritable(),
          ai.isIs(),
          ai.getDescriptor());
    }
  }

  private static OpenMBeanParameterInfo[]
  paramsToOpenParams(MBeanParameterInfo[] params) {
    if (params instanceof OpenMBeanParameterInfo[]) {
      return (OpenMBeanParameterInfo[]) params;
    }
    OpenMBeanParameterInfo[] oparams =
        new OpenMBeanParameterInfoSupport[params.length];
    System.arraycopy(params, 0, oparams, 0, params.length);
    return oparams;
  }

  // ------------------------------------------------------------------
  // Build the custom MBeanConstructorInfo[]
  // ------------------------------------------------------------------
  private MBeanConstructorInfo[]
  getConstructors(MBeanInfo info, Object impl) {
    final MBeanConstructorInfo[] ctors =
        getConstructors(info.getConstructors(), impl);
    if (ctors == null) {
      return null;
    }
    final int ctorlen = ctors.length;
    final MBeanConstructorInfo[] nctors = new MBeanConstructorInfo[ctorlen];
    for (int i = 0; i < ctorlen; i++) {
      final MBeanConstructorInfo c = ctors[i];
      final MBeanParameterInfo[] params = c.getSignature();
      final MBeanParameterInfo[] nps;
      if (params != null) {
        final int plen = params.length;
        nps = new MBeanParameterInfo[plen];
        for (int ii = 0; ii < plen; ii++) {
          MBeanParameterInfo p = params[ii];
          nps[ii] = customize(p,
              getParameterName(c, p, ii),
              getDescription(c, p, ii));
        }
      } else {
        nps = null;
      }
      nctors[i] =
          customize(c, getDescription(c), nps);
    }
    return nctors;
  }

  // ------------------------------------------------------------------
  // Build the custom MBeanOperationInfo[]
  // ------------------------------------------------------------------
  private MBeanOperationInfo[] getOperations(MBeanInfo info) {
    final MBeanOperationInfo[] ops = info.getOperations();
    if (ops == null) {
      return null;
    }
    final int oplen = ops.length;
    final MBeanOperationInfo[] nops = new MBeanOperationInfo[oplen];
    for (int i = 0; i < oplen; i++) {
      final MBeanOperationInfo o = ops[i];
      final MBeanParameterInfo[] params = o.getSignature();
      final MBeanParameterInfo[] nps;
      if (params != null) {
        final int plen = params.length;
        nps = new MBeanParameterInfo[plen];
        for (int ii = 0; ii < plen; ii++) {
          MBeanParameterInfo p = params[ii];
          nps[ii] = customize(p,
              getParameterName(o, p, ii),
              getDescription(o, p, ii));
        }
      } else {
        nps = null;
      }
      nops[i] = customize(o, getDescription(o), nps, getImpact(o));
    }
    return nops;
  }

  // ------------------------------------------------------------------
  // Build the custom MBeanAttributeInfo[]
  // ------------------------------------------------------------------
  private MBeanAttributeInfo[] getAttributes(MBeanInfo info) {
    final MBeanAttributeInfo[] atts = info.getAttributes();
    if (atts == null) {
      return null; // should not happen
    }
    final MBeanAttributeInfo[] natts;
    final int attlen = atts.length;
    natts = new MBeanAttributeInfo[attlen];
    for (int i = 0; i < attlen; i++) {
      final MBeanAttributeInfo a = atts[i];
      natts[i] = customize(a, getDescription(a));
    }
    return natts;
  }

  /**
   * <p>Allows the MBean to perform any operations it needs before
   * being registered in the MBean server.  If the name of the MBean
   * is not specified, the MBean can provide a name for its
   * registration.  If any exception is raised, the MBean will not be
   * registered in the MBean server.</p>
   *
   * <p>The default implementation of this method returns the {@code name}
   * parameter.  It does nothing else for
   * Standard MBeans.  For MXBeans, it records the {@code MBeanServer}
   * and {@code ObjectName} parameters so they can be used to translate
   * inter-MXBean references.</p>
   *
   * <p>It is good practice for a subclass that overrides this method
   * to call the overridden method via {@code super.preRegister(...)}.
   * This is necessary if this object is an MXBean that is referenced
   * by attributes or operations in other MXBeans.</p>
   *
   * @param server The MBean server in which the MBean will be registered.
   * @param name The object name of the MBean.  This name is null if the name parameter to one of
   * the <code>createMBean</code> or <code>registerMBean</code> methods in the {@link MBeanServer}
   * interface is null.  In that case, this method must return a non-null ObjectName for the new
   * MBean.
   * @return The name under which the MBean is to be registered. This value must not be null.  If
   * the <code>name</code> parameter is not null, it will usually but not necessarily be the
   * returned value.
   * @throws IllegalArgumentException if this is an MXBean and {@code name} is null.
   * @throws InstanceAlreadyExistsException if this is an MXBean and it has already been registered
   * under another name (in this MBean Server or another).
   * @throws Exception no other checked exceptions are thrown by this method but {@code Exception}
   * is declared so that subclasses can override the method and throw their own exceptions.
   * @since 1.6
   */
  public ObjectName preRegister(MBeanServer server, ObjectName name)
      throws Exception {
    mbean.register(server, name);
    return name;
  }

  /**
   * <p>Allows the MBean to perform any operations needed after having been
   * registered in the MBean server or after the registration has failed.</p>
   *
   * <p>The default implementation of this method does nothing for
   * Standard MBeans.  For MXBeans, it undoes any work done by
   * {@link #preRegister preRegister} if registration fails.</p>
   *
   * <p>It is good practice for a subclass that overrides this method
   * to call the overridden method via {@code super.postRegister(...)}.
   * This is necessary if this object is an MXBean that is referenced
   * by attributes or operations in other MXBeans.</p>
   *
   * @param registrationDone Indicates whether or not the MBean has been successfully registered in
   * the MBean server. The value false means that the registration phase has failed.
   * @since 1.6
   */
  public void postRegister(Boolean registrationDone) {
    if (!registrationDone) {
      mbean.unregister();
    }
  }

  /**
   * <p>Allows the MBean to perform any operations it needs before
   * being unregistered by the MBean server.</p>
   *
   * <p>The default implementation of this method does nothing.</p>
   *
   * <p>It is good practice for a subclass that overrides this method
   * to call the overridden method via {@code super.preDeregister(...)}.</p>
   *
   * @throws Exception no checked exceptions are throw by this method but {@code Exception} is
   * declared so that subclasses can override this method and throw their own exceptions.
   * @since 1.6
   */
  public void preDeregister() throws Exception {
  }

  /**
   * <p>Allows the MBean to perform any operations needed after having been
   * unregistered in the MBean server.</p>
   *
   * <p>The default implementation of this method does nothing for
   * Standard MBeans.  For MXBeans, it removes any information that
   * was recorded by the {@link #preRegister preRegister} method.</p>
   *
   * <p>It is good practice for a subclass that overrides this method
   * to call the overridden method via {@code super.postRegister(...)}.
   * This is necessary if this object is an MXBean that is referenced
   * by attributes or operations in other MXBeans.</p>
   *
   * @since 1.6
   */
  public void postDeregister() {
    mbean.unregister();
  }

  //
  // MBeanInfo immutability
  //

  /**
   * Cached results of previous calls to immutableInfo. This is
   * a WeakHashMap so that we don't prevent a class from being
   * garbage collected just because we know whether its MBeanInfo
   * is immutable.
   */
  private static final Map<Class<?>, Boolean> mbeanInfoSafeMap =
      new WeakHashMap<Class<?>, Boolean>();

  /**
   * Return true if {@code subclass} is known to preserve the immutability
   * of the {@code MBeanInfo}. The {@code subclass} is considered to have
   * an immutable {@code MBeanInfo} if it does not override any of the
   * getMBeanInfo, getCachedMBeanInfo, cacheMBeanInfo and getNotificationInfo
   * methods.
   */
  static boolean immutableInfo(Class<? extends StandardMBean> subclass) {
    if (subclass == StandardMBean.class ||
        subclass == StandardEmitterMBean.class) {
      return true;
    }
    synchronized (mbeanInfoSafeMap) {
      Boolean safe = mbeanInfoSafeMap.get(subclass);
      if (safe == null) {
        try {
          MBeanInfoSafeAction action =
              new MBeanInfoSafeAction(subclass);
          safe = AccessController.doPrivileged(action);
        } catch (Exception e) { // e.g. SecurityException
                    /* We don't know, so we assume it isn't.  */
          safe = false;
        }
        mbeanInfoSafeMap.put(subclass, safe);
      }
      return safe;
    }
  }

  static boolean overrides(Class<?> subclass, Class<?> superclass,
      String name, Class<?>... params) {
    for (Class<?> c = subclass; c != superclass; c = c.getSuperclass()) {
      try {
        c.getDeclaredMethod(name, params);
        return true;
      } catch (NoSuchMethodException e) {
        // OK: this class doesn't override it
      }
    }
    return false;
  }

  private static class MBeanInfoSafeAction
      implements PrivilegedAction<Boolean> {

    private final Class<?> subclass;

    MBeanInfoSafeAction(Class<?> subclass) {
      this.subclass = subclass;
    }

    public Boolean run() {
      // Check for "void cacheMBeanInfo(MBeanInfo)" method.
      //
      if (overrides(subclass, StandardMBean.class,
          "cacheMBeanInfo", MBeanInfo.class)) {
        return false;
      }

      // Check for "MBeanInfo getCachedMBeanInfo()" method.
      //
      if (overrides(subclass, StandardMBean.class,
          "getCachedMBeanInfo", (Class<?>[]) null)) {
        return false;
      }

      // Check for "MBeanInfo getMBeanInfo()" method.
      //
      if (overrides(subclass, StandardMBean.class,
          "getMBeanInfo", (Class<?>[]) null)) {
        return false;
      }

      // Check for "MBeanNotificationInfo[] getNotificationInfo()"
      // method.
      //
      // This method is taken into account for the MBeanInfo
      // immutability checks if and only if the given subclass is
      // StandardEmitterMBean itself or can be assigned to
      // StandardEmitterMBean.
      //
      if (StandardEmitterMBean.class.isAssignableFrom(subclass)) {
        if (overrides(subclass, StandardEmitterMBean.class,
            "getNotificationInfo", (Class<?>[]) null)) {
          return false;
        }
      }
      return true;
    }
  }
}
